﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using ComTypes = System.Runtime.InteropServices.ComTypes;
using Microsoft.Win32.SafeHandles;
using System.Security.Cryptography;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Windows.Forms;
using System.DirectoryServices;
namespace MacomberMapSystem.Common.Internals
{
    /// <summary>
    /// This class provides interfaces to active directory and encryption services to support MM's interfaces to ERCOT
    /// </summary>
    public class MM_System_Interface
    {
        #region Imports
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool GetSystemTimes(out System.Runtime.InteropServices.ComTypes.FILETIME lpIdleTime, out ComTypes.FILETIME lpKernelTime, out ComTypes.FILETIME lpUserTime);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern SafeFileHandle CreateFile(String pipeName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplate);

        [DllImport("psapi.dll", SetLastError = true)]
        static extern bool GetProcessMemoryInfo(IntPtr hProcess, out PROCESS_MEMORY_COUNTERS counters, uint size);

        [DllImport("kernel32.dll")]
        private static extern IntPtr GetCurrentProcess();

        [StructLayout(LayoutKind.Sequential, Size = 40)]
        private struct PROCESS_MEMORY_COUNTERS
        {
            public uint cb;
            public uint PageFaultCount;
            public int PeakWorkingSetSize;
            public int WorkingSetSize;
            public int QuotaPeakPagedPoolUsage;
            public int QuotaPagedPoolUsage;
            public int QuotaPeakNonPagedPoolUsage;
            public int QuotaNonPagedPoolUsage;
            public int PagefileUsage;
            public int PeakPagefileUsage;
        }

        #endregion

        #region Variable declarations
        /// <summary>The previous system kernel usage</summary>
        private static ComTypes.FILETIME _prevSysKernel;

        /// <summary>The previous system user usage</summary>
        private static ComTypes.FILETIME _prevSysUser;

        private static TimeSpan _prevProcTotal;
        private static Int16 _cpuUsage;
        private static DateTime _lastRun;
        private static long _runCount;
        private static long _lastWorkingSet;
        #endregion

        #region Constants
        private const uint GENERIC_READ = (0x80000000);
        private const uint GENERIC_WRITE = (0x40000000);
        private const uint OPEN_EXISTING = 3;
        private const int BUFFER_SIZE = 4096;
        private const uint FILE_FLAG_OVERLAPPED = (0x40000000);

        /// <summary>The pipe name for Macomber Map</summary>
        public const string MacomberMap = "\\\\.\\pipe\\MacomberMap";

        #endregion

        #region Interface to Active Directory
        /// <summary>
        /// Determine whether given credentials are sufficient to allow for Data Integrated Viewer access
        /// </summary>
        /// <param name="Username"></param>
        /// <param name="Password"></param>
        /// <param name="Domain"></param>
        /// <returns></returns>
        public static bool ValidateCredentials(String Username, String Password, String Domain)
        {
#if BypassActiveDirectory
            return true;
#else
            using (DirectoryEntry adEntry = new DirectoryEntry("LDAP://" + Domain, Username, Password))
            using (DirectorySearcher adSearcher = new DirectorySearcher(adEntry))
                try
                {
                    SearchResult sR = adSearcher.FindOne();
                    adEntry.Close();
                    return true;
                }
                catch (Exception)
                {
                    adEntry.Close();
                    return false;
                }
#endif
        }
        #endregion

        #region Encryption/Decryption routines
        /// <summary>
        /// Decrypt the specified encrypted text
        /// </summary>
        /// <param name="EncryptedText">The encrypted text</param>
        /// <returns></returns>
        public static String Decrypt(String EncryptedText)
        {
            if (EncryptedText.LastIndexOf('=') == -1)
                return EncryptedText;
            TripleDESCryptoServiceProvider TDes = new TripleDESCryptoServiceProvider();
            //TODO: Place TDes.IV and Key.
            byte[] inBytes = Convert.FromBase64String(EncryptedText);
            return Encoding.UTF8.GetString(TDes.CreateDecryptor().TransformFinalBlock(inBytes, 0, inBytes.Length));

        }

        /// <summary>
        /// Encrypt the specified clear text
        /// </summary>
        /// <param name="ClearText">The decrypted text</param>
        /// <returns></returns>
        public static String Encrypt(String ClearText)
        {
            TripleDESCryptoServiceProvider TDes = new TripleDESCryptoServiceProvider();
            //TODO: Place TDes.IV and Key.
            byte[] EncryptedBytes = Encoding.UTF8.GetBytes(ClearText);
            return Convert.ToBase64String(TDes.CreateEncryptor().TransformFinalBlock(EncryptedBytes, 0, EncryptedBytes.Length));
        }
        #endregion

        #region CPU Usage routines
        /// <summary>
        /// Initialize our CPU monitoring system
        /// </summary>
        public static void Initialize()
        {
            _cpuUsage = -1;
            _lastRun = DateTime.MinValue;
            _prevSysUser.dwHighDateTime = _prevSysUser.dwLowDateTime = 0;
            _prevSysKernel.dwHighDateTime = _prevSysKernel.dwLowDateTime = 0;
            _prevProcTotal = TimeSpan.MinValue;
            _runCount = 0;
        }

        /// <summary>
        /// Determine the current CPU usage in percent
        /// </summary>
        /// <param name="WorkingSet">The working set</param>
        /// <returns></returns>
        public static short GetCPUUsage(out long WorkingSet)
        {
           //Determine our current process
            IntPtr CurrentProcess = GetCurrentProcess();
            PROCESS_MEMORY_COUNTERS memoryCounters;
            memoryCounters.cb = 40;
            
            short cpuCopy = _cpuUsage;
            if (Interlocked.Increment(ref _runCount) == 1)
            {
                if (!EnoughTimePassed)
                {
                    Interlocked.Decrement(ref _runCount);
                    WorkingSet = _lastWorkingSet;
                    return cpuCopy;
                }

                ComTypes.FILETIME sysIdle, sysKernel, sysUser;
                TimeSpan procTime;
                procTime = TimeSpan.FromSeconds(2);

                if (GetProcessMemoryInfo(CurrentProcess, out memoryCounters, memoryCounters.cb))
                    WorkingSet = _lastWorkingSet = memoryCounters.WorkingSetSize;
                else
                    WorkingSet = _lastWorkingSet = GC.GetTotalMemory(false); // process.WorkingSet64;
               
                using (Process process = Process.GetCurrentProcess())
                {
                    procTime = process.TotalProcessorTime;
                  //  WorkingSet = _lastWorkingSet = process.WorkingSet64;
                    //WorkingSet = 2;
                }
                
                if (!GetSystemTimes(out sysIdle, out sysKernel, out sysUser))
                {
                    Interlocked.Decrement(ref _runCount);
                    WorkingSet = _lastWorkingSet;
                    return cpuCopy;
                }

                if (!IsFirstRun)
                {

                    UInt64 sysKernelDiff = SubtractTimes(sysKernel, _prevSysKernel);
                    UInt64 sysUserDiff = SubtractTimes(sysUser, _prevSysUser);
                    UInt64 sysTotal = sysKernelDiff + sysUserDiff;
                    Int64 procTotal = procTime.Ticks - _prevProcTotal.Ticks;
                    if (sysTotal > 0)
                        _cpuUsage = (short)((100.0 * procTotal) / sysTotal);
                }

                _prevProcTotal = procTime;
                _prevSysKernel = sysKernel;
                _prevSysUser = sysUser;
                _lastRun = DateTime.Now;
                cpuCopy = _cpuUsage;
            }
            else
                WorkingSet = _lastWorkingSet;


            Interlocked.Decrement(ref _runCount);
            return cpuCopy;
        }

        /// <summary>
        /// Subtract two times
        /// </summary>
        /// <param name="a">Time 1</param>
        /// <param name="b">Time 2</param>
        /// <returns></returns>
        private static UInt64 SubtractTimes(ComTypes.FILETIME a, ComTypes.FILETIME b)
        {
            UInt64 aInt = ((UInt64)(a.dwHighDateTime << 32)) | (UInt64)a.dwLowDateTime;
            UInt64 bInt = ((UInt64)(b.dwHighDateTime << 32)) | (UInt64)b.dwLowDateTime;
            return aInt - bInt;
        }

        /// <summary>
        /// Determine whether enough time has passed for an accurate reading
        /// </summary>
        private static bool EnoughTimePassed
        {
            get
            {
                const int minimumElapsedMS = 250;
                TimeSpan sinceLast = DateTime.Now - _lastRun;
                return sinceLast.TotalMilliseconds > minimumElapsedMS;
            }
        }

        /// <summary>
        /// Determine whether this is the first CPU run
        /// </summary>
        private static bool IsFirstRun
        {
            get { return (_lastRun == DateTime.MinValue); }
        }
        #endregion

        #region Macomber Map interface routines
        /// <summary>
        /// Send one or more lines to a pipe.
        /// </summary>
        /// <param name="PipeName"></param>
        /// <param name="Lines"></param>
        public static void SendMessage(String PipeName, params String[] Lines)
        {
            SafeFileHandle pipeHandle = CreateFile(PipeName, GENERIC_READ | GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, IntPtr.Zero);
            if (pipeHandle.IsInvalid)
                MessageBox.Show("Unable to open message pipe!", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Stop);
            else
                using (FileStream fStream = new FileStream(pipeHandle, FileAccess.ReadWrite, BUFFER_SIZE, true))
                using (StreamWriter sW = new StreamWriter(fStream))
                    for (int a = 0; a < Lines.Length; a++)
                        sW.WriteLine(Lines[a]);
        }
        #endregion

        #region Error reporting
        /// <summary>
        /// Build a string export of an error
        /// </summary>
        /// <param name="ex"></param>
        /// <returns></returns>
        public static String ConvertError(Exception ex)
        {
            StringBuilder sB = new StringBuilder();
            int Tab = 0;
            String InLine;
            while (ex != null)
            {
                sB.AppendLine();
                sB.AppendLine(new String('\t', Tab) + "---------------------------");                                
                sB.AppendLine(new string('\t', Tab) + ex.Message);
                if (!String.IsNullOrEmpty(ex.StackTrace))
                using (StringReader sRd = new StringReader(ex.StackTrace))
                    while (!String.IsNullOrEmpty(InLine = sRd.ReadLine()))
                        sB.AppendLine(new string('\t', Tab) + InLine);
                sB.AppendLine();
                ex = ex.InnerException;
                Tab++;
            }
            return sB.ToString();            
        }
        #endregion
    }
}
